/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adri�n Noguero (adrian.noguero@esi.es)
 *     
 *******************************************************************************/
package es.esi.gemde.modeltransformator.service.impl;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;

import es.esi.gemde.core.resources.IGemdeResource.ResourceType;
import es.esi.gemde.modeltransformator.ModelTransformatorPlugin;
import es.esi.gemde.modeltransformator.exceptions.NoPreviousTransformationException;
import es.esi.gemde.modeltransformator.exceptions.NoSuchEngineException;
import es.esi.gemde.modeltransformator.exceptions.TransformationEngineException;
import es.esi.gemde.modeltransformator.service.AbstractJavaTransformation;
import es.esi.gemde.modeltransformator.service.IModelTransformationService;
import es.esi.gemde.modeltransformator.service.ITransformation;
import es.esi.gemde.modeltransformator.service.ITransformationEngine;

/**
 * Implementation of the {@link IModelTransformationService} interface.
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0
 * @since 1.0
 *
 */
public class ModelTransformationService implements IModelTransformationService {

	private HashMap<String,ITransformation> transformationRepository;
	
	// Parameters used to store the parameters of the last transformation
	private List<EObject> lastInputs;
	private ITransformation lastTransformation;
	private ITransformationEngine lastEngine;
	private String lastOutputPath;
	
	/**
	 * Constructor. Initializes the repository.
	 *
	 */
	public ModelTransformationService() {
		transformationRepository = new HashMap<String, ITransformation>();
		lastInputs = null;
		lastEngine = null;
		lastOutputPath = null;
		lastTransformation = null;
	}
	
	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#addTransformation(es.esi.gemde.modeltransformator.service.ITransformation)
	 */
	@Override
	public void addTransformation(ITransformation newTransformation) throws IllegalArgumentException {
		if (newTransformation == null) {
			throw new IllegalArgumentException("A null transformation was provided");
		}
		
		if (!transformationRepository.containsKey(newTransformation.getName())) {
			transformationRepository.put(newTransformation.getName(), newTransformation);
		}
		else {
			throw new IllegalArgumentException("A transformation with the same name is already in the repository");
		}
		
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#addSessionTransformation(es.esi.gemde.modeltransformator.service.ITransformation)
	 */
	@Override
	public void addSessionTransformation(ITransformation newTransformation)
			throws IllegalArgumentException {
		if (!newTransformation.getResourceType().equals(ResourceType.PROTECTED)) {
			throw new IllegalArgumentException("Provided session transformation is not protected");
		}
		addTransformation(newTransformation);
		
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#transformationExists(java.lang.String)
	 */
	@Override
	public boolean transformationExists(String transformationName) {
		return transformationRepository.containsKey(transformationName);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#removeTransformation(java.lang.String)
	 */
	@Override
	public void removeTransformation(String name) {
		if (transformationRepository.containsKey(name)) {
			transformationRepository.remove(name);
		}
		
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#getTransformationRepository()
	 */
	@Override
	public ITransformation[] getTransformationRepository() {
		return transformationRepository.values().toArray(new ITransformation[0]);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#registerEngine(es.esi.gemde.modeltransformator.service.ITransformationEngine)
	 */
	@Override
	public void registerEngine(ITransformationEngine engine) throws IllegalArgumentException {
		if (engine == null) {
			throw new IllegalArgumentException("A null engine implementation was provided");
		}
		
		if (engine.getName() == null || engine.getName().equals("")) {
			throw new IllegalArgumentException("Provided engine name is not valid");
		}
		
		// Delegate to the Engine Repository
		EngineRepository.getInstance().registerEngine(engine);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#unregisterEngine(java.lang.String)
	 */
	@Override
	public void unregisterEngine(String engineName) {
		// Delegate to the Engine Repository
		EngineRepository.getInstance().unregisterEngine(engineName);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#getAvailableEngines()
	 */
	@Override
	public String[] getAvailableEngines() {
		// Delegate to the Engine Repository
		return EngineRepository.getInstance().getEngineNames();
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#executeTransformation(java.util.List, java.lang.String, java.lang.String)
	 */
	@Override
	public IStatus executeTransformation(List<EObject> inputs, String transformationName, String engineName, String outputPath) throws IllegalArgumentException, TransformationEngineException, NoSuchEngineException, IOException {
		
		// Check the correctness of all the inputs
		if (engineName == null || engineName.equals("")) {
			throw new IllegalArgumentException("A null or empty engine name was provided");
		}
		
		if (transformationName == null || transformationName.equals("") || !transformationExists(transformationName)) {
			throw new IllegalArgumentException("An invalid transformation name was provided");
		}
		
		ITransformationEngine engine = EngineRepository.getInstance().getEngineByName(engineName);
		
		if (engine == null) {
			throw new NoSuchEngineException("The provided engine name is not registered in the service");
		}
		
		File directory = new File(outputPath);
		if (!directory.exists() || !directory.isDirectory() || !directory.canWrite()) {
			throw new IOException("The provided path does not point to a valid directory");
		}
		
		// Delegate the action to the corresponding engine. All provided inputs are valid.
		IStatus result = null;
		try {
			result = engine.executeTransformation(inputs.toArray(new EObject[0]), transformationRepository.get(transformationName), outputPath);
		}
		catch (IllegalArgumentException iae) {
			throw iae;
		}
		catch (TransformationEngineException tee) {
			throw tee;
		}
		
		refreshInputs(inputs);
		
		// If no exceptions were caught the transformation is valid and repeatable.
		lastInputs = inputs;
		lastEngine = engine;
		lastTransformation = transformationRepository.get(transformationName);
		lastOutputPath = outputPath;
		
		return result;
	}
	
	/**
	 * This method is used to reload the input models before saving them, to 
	 * avoid any non-saved changes in the inputs affect subsequent transformation calls.
	 * 
	 * @param inputs. The list of input EObjects
	 */
	private void refreshInputs(List<EObject> inputs) {
		Vector<EObject> temp = new Vector<EObject>();
		ResourceSet rs = new ResourceSetImpl();
		try {
			for (EObject o1 : inputs) {
				Resource r = rs.getResource(o1.eResource().getURI(), true);
				TreeIterator<EObject> i = r.getAllContents();
				if (i.hasNext()) {
					EObject o2 = i.next();
					if (EcoreUtil.equals(o1, o2)) {
						temp.add(o2);
					}
				}
			}
		}
		catch (NullPointerException e) {
			// This means that some of the resources have been created in memory, but still not saved. Thus, we won't refresh.
			return;
		}
		
		if (temp.size() == inputs.size()) {
			for (int i = 0; i < temp.size(); i++) {
				inputs.set(i, temp.get(i));
			}
		}
		
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#getEngineImage(java.lang.String)
	 */
	@Override
	public IStatus executeLastTransformation () throws TransformationEngineException, NoPreviousTransformationException {
		
		if (lastInputs != null) {
			refreshInputs(lastInputs);
		}
		
		// Check the validity of the
		if (lastEngine == null || lastTransformation == null || lastOutputPath == null || lastInputs == null) {
			throw new NoPreviousTransformationException();
		}
		
		IStatus result = null;
		try {
			result = lastEngine.executeTransformation(lastInputs.toArray(new EObject[0]), lastTransformation, lastOutputPath);
		}
		catch (IllegalArgumentException iae) {
			throw new NoPreviousTransformationException();
		}
		
		refreshInputs(lastInputs);
		
		return result;
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#getEngineImage(java.lang.String)
	 */
	@Override
	public Image getEngineImage(String name) {
		if (AbstractJavaTransformation.ENGINE_NAME.equals(name)) {
			ImageDescriptor id = ModelTransformatorPlugin.imageDescriptorFromPlugin(ModelTransformatorPlugin.PLUGIN_ID, AbstractJavaTransformation.ICON_PATH);
			if (id != null) {
				return id.createImage();
			}
			else {
				return null;
			}
		}
		return EngineRepository.getInstance().getEngineImage(name);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#checkTransformationValidity(java.lang.String, java.net.URI)
	 */
	@Override
	public boolean checkTransformationValidity(String engine, URI transformationURI) {
		List<URI> uris = new Vector<URI>();
		uris.add(transformationURI);
		return EngineRepository.getInstance().getEngineByName(engine).checkTransformationURIValidity(uris);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modeltransformator.service.IModelTransformationService#checkTransformationValidity(java.lang.String, java.util.List)
	 */
	@Override
	public boolean checkTransformationValidity(String engine, List<URI> transformationURIs) {
		return EngineRepository.getInstance().getEngineByName(engine).checkTransformationURIValidity(transformationURIs);
	}

	@Override
	public boolean checkTransformationParamsValidity(String engine,
			HashMap<String, EClass> inputs, HashMap<String, EClass> outputs,
			HashMap<String, String> options) {
		return EngineRepository.getInstance().getEngineByName(engine).checkTransformationParamsValidity(inputs, outputs, options);
	}

	@Override
	public ITransformation getTransformation(String transformationName) {
		return transformationRepository.get(transformationName);
	}

}
